/*
                   SCC-650 - Computação Gráfica
 *              Profa. Agma Traina, PAE Alceu Ferraz Costa
 * 
 *              Trabalho 1 - Conversão Matricial de Circunferências
 * 
 *              Alisson Prado da Cruz                   7173890
 *              Guilherme Machado Gagliardi             7126754 
                                        
 */

/**********************************************************************************************
   ATENCAO - a biblioteca dislin, utilizada para a geracao do arquivo PDF com os graficos dos
 *           resultados obtidos, foi de dificil inclusao em ambiente Ubuntu/Linux 11.10.
 *           Erros de compilacao devidos a vinculacao incorreta de tal biblioteca foram frequentes.
 *           Sua presenca nao e vital para o funcionamento do programa; por isso, as ocorrencias
 *           de sua utilizacao foram sinalizadas no corpo do codigo e estão comentadas por padrão, 
 *           podendo ser removidas caso ocorram dificuldades em sua utilizacao.
 ***********************************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h> //Glut
#include <math.h>
/*****************************************************************************************/
//#include "dislin.h" //dislin, biblioteca utilizada para geracao dos graficos de resultados - REMOVER CASO HAJA PROBLEMAS NA COMPILACAO
/*****************************************************************************************/
#include <time.h>

#define RAIO_MIN 10
#define RAIO_MAX 200
#define PASSO_RAIO 10 //diferenca entre valores sucessivos de raio considerados
#define NUM_REPETICOES 100
#define ALTURA_JANELA 500
#define LARGURA_JANELA 500

//variavel utilizada para controlar quantas vezes a funcao display() ja foi chamada
int vez_disp;

//tipo que define as amostras que serão usadas para afericao de performance

typedef struct {
    float raio[(RAIO_MAX - RAIO_MIN) / PASSO_RAIO + 1];
    float tempo_medio[(RAIO_MAX - RAIO_MIN) / PASSO_RAIO + 1];
} amostra;

//funcao utilizada para o calculo do periodo decorrido entre o instante "inicio" e o instante "fim"

struct timespec diferencaEntreInstantes(struct timespec inicio, struct timespec fim) {

    struct timespec temp;
    if ((fim.tv_nsec - inicio.tv_nsec) < 0) {
        temp.tv_sec = fim.tv_sec - inicio.tv_sec - 1;
        temp.tv_nsec = 1000000000 + fim.tv_nsec - inicio.tv_nsec;
    } else {
        temp.tv_sec = fim.tv_sec - inicio.tv_sec;
        temp.tv_nsec = fim.tv_nsec - inicio.tv_nsec;
    }

    return temp;
}

//funcao utilizada para incorporar as afericoes de tempo para cada algoritmo e cada raio ao arquivo "Relatorio de Resultados.txt"

void geraRelatorioResultados(amostra amostras_alg_retangular, amostra amostras_alg_polar, amostra amostras_alg_incremental) {

    int k;
    FILE *log;

    //obtencao da data e do horario do sistema
    time_t now = time(NULL);
    struct tm *t = localtime(&now);

    log = fopen("Relatorio de Resultados.txt", "a+");

    if (log == NULL)
        printf("> \nERRO AO ABRIR ARQUIVO\n <");

    fprintf(log, ">> RELATORIO DE RESULTADOS - %d/%d/%d %d:%d:%d<< \n\n", t->tm_mday, t->tm_mon + 1, t->tm_year + 1900, t->tm_hour, t->tm_min, t->tm_sec);

    /*para cada algoritmo, as amostras recebidas sao escritas no arquivo*/

    fputs(" ## Algoritmo Incremental ##\n", log);
    for (k = 0; k <= (RAIO_MAX - RAIO_MIN) / PASSO_RAIO; k++)
        fprintf(log, "\n  Raio: %3.0f  Tempo Medio: %3.2f us", amostras_alg_incremental.raio[k], amostras_alg_incremental.tempo_medio[k]);

    fputs("\n\n ## Algoritmo Retangular ##\n", log);
    for (k = 0; k <= (RAIO_MAX - RAIO_MIN) / PASSO_RAIO; k++)
        fprintf(log, "\n  Raio: %3.0f  Tempo Medio: %3.2f us", amostras_alg_retangular.raio[k], amostras_alg_retangular.tempo_medio[k]);

    fputs("\n\n ## Algoritmo Polar ##\n", log);
    for (k = 0; k <= (RAIO_MAX - RAIO_MIN) / PASSO_RAIO; k++)
        fprintf(log, "\n  Raio: %3.0f  Tempo Medio: %3.2f us", amostras_alg_polar.raio[k], amostras_alg_polar.tempo_medio[k]);

    fputs("\n\n\n\n", log);
    fclose(log);
}

/*
 **
 **
 **
 **/
//funcao utilizada para gerar os graficos com as amostras de tempo recebidas - REMOVER CASO HAJA PROBLEMAS NA COMPILACAO
/*
void geraGraficosResultados(amostra amostras_alg_retangular, amostra amostras_alg_polar, amostra amostras_alg_incremental) {

    int nya = 2600, nx, ny, ic;
    char *ctit = "Conversao Matricial de Circunferencias";

    //formatacao dos graficos e identificacao dos titulos de eixos, etc
    setpag("da4p");
    metafl("PDF");
    scrmod("revers");
    setfil("Graficos de Resultados");
    disini();
    errmod("ALL", "OFF");
    complx();
    pagera();
    incmrk(1);
    hsymbl(10);
    titlin(ctit, 2);
    axslen(1500, 600);
    setgrf("names", "names", "line", "line");
    ic = intrgb(255, 255, 255);
    axsbgd(ic);
    hname(30);

    name("Raio", "x");
    name("Tempo (us)", "y");

    labdig(-2, "x");
    ticks(0, "x");
    ticks(10, "y");

    height(25);

    polcrv("LINEAR");
    marker(16);

    //geracao do grafico com a afericao para cada algoritmo

    //algoritmo Incremental ********************************************************
    axspos(380, nya - 0 * 800 + 50);
    graf(RAIO_MIN, RAIO_MAX, 0.0, PASSO_RAIO, 0.0, 140.0, 0.0, 20);
    nx = nxposn(15.0);
    ny = nyposn(150.0);
    messag("Algoritmo Incremental", nx, ny);
    color("green");
    curve(amostras_alg_incremental.raio, amostras_alg_incremental.tempo_medio, (RAIO_MAX - RAIO_MIN) / PASSO_RAIO + 1);
    color("fore");
    endgrf();

    //algoritmo Retangular ********************************************************
    axspos(380, nya - 1 * 800 + 50);
    graf(RAIO_MIN, RAIO_MAX, 0.0, PASSO_RAIO, 0.0, 140.0, 0.0, 20);
    nx = nxposn(15.0);
    ny = nyposn(150.0);
    messag("Algoritmo Retangular", nx, ny);
    color("red");
    curve(amostras_alg_retangular.raio, amostras_alg_retangular.tempo_medio, (RAIO_MAX - RAIO_MIN) / PASSO_RAIO + 1);
    color("fore");
    endgrf();

    //algoritmo Polar *************************************************************
    axspos(380, nya - 2 * 800 + 50);
    graf(RAIO_MIN, RAIO_MAX, 0.0, PASSO_RAIO, 0.0, 140.0, 0.0, 20);
    nx = nxposn(15.0);
    ny = nyposn(150.0);
    messag("Algoritmo Polar", nx, ny);
    color("blue");
    curve(amostras_alg_polar.raio, amostras_alg_polar.tempo_medio, (RAIO_MAX - RAIO_MIN) / PASSO_RAIO + 1);
    color("fore");
    height(50);
    title();
    endgrf();

    disfin();
}
*/
/**
 **
 **
 **
 */

//funcao utilizada para a plotagem dos pontos da circunferencia, baseando-se na simetria de oito vias
void circlePoints(float x, float y) {

    glVertex2f(x, y);
    glVertex2f(x, -y);
    glVertex2f(-x, y);
    glVertex2f(-x, -y);
    glVertex2f(y, x);
    glVertex2f(y, -x);
    glVertex2f(-y, x);
    glVertex2f(-y, -x);
}

//definicao da cor de fundo como branca

void init2D(float r, float g, float b) {

    glClearColor(r, g, b, 0.0);
}

//algoritmo polar para conversao matricial das circunferencias

void circPolar(amostra *amostras_alg_polar) {

    int i, k = 0;
    float x, y, tempo_medio, j, raio;
    struct timespec tinic, tfinal;

    //limpa a tela
    glClear(GL_COLOR_BUFFER_BIT);

    //cor azul, se e a segunda execucao de display(); a cor sera branca, caso contrario
    if (vez_disp == 2)
        glColor3f(0.0, 0.0, 1.0);

    glBegin(GL_POINTS);

    //para cada valor de raio considerado (erros foram encontrados sem a utilizacao dos casts para o tipo float)
    for (raio = (float) RAIO_MIN; raio <= (float) RAIO_MAX; raio += (float) PASSO_RAIO) {

        //tempo medio das execucoes
        tempo_medio = 0.0;

        //para cada execucao
        for (i = 0; i < NUM_REPETICOES; i++) {

            //registra-se o instante inicial em tinic
            clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);

            //o angulo varia de 90 graus a 45 graus, em passos de tamanho 50/raio
            for (j = 90; j >= 45; j -= 50 / raio) {

                //equacao polar da circunferencia
                x = raio * cos(j * M_PI / 180.0f);
                y = raio * sin(j * M_PI / 180.0f);

                //os oito pontos simetricos sao plotados. As coordenadas sao normalizadas para o tamanho da janela
                circlePoints(x / (LARGURA_JANELA / 2), y / (ALTURA_JANELA / 2));
            }

            //registra-se o instante final em tfinal 
            clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

            //o tempo medio das execucoes e incrementado
            tempo_medio += (float) diferencaEntreInstantes(tinic, tfinal).tv_nsec / NUM_REPETICOES;
        }

        //o valor do raio e o tempo medio de execucao a ele associado sao registrados em amostra
        amostras_alg_polar->raio[k] = raio;
        amostras_alg_polar->tempo_medio[k++] = tempo_medio / 1000;
    }

    glEnd();

    glFlush();

    //caso seja a segunda chamada a funcao display(), faz-se uma pausa de dois segundos
    if (vez_disp == 2)
        sleep(2);
}


//algoritmo retangular para conversao matricial das circunferencias

void circRetangular(amostra *amostras_alg_retangular) {

    int i, k = 0;
    float x, y, tempo_medio, j, raio;
    struct timespec tinic, tfinal;

    //limpa a tela
    glClear(GL_COLOR_BUFFER_BIT);

    //cor vermelha, se e a segunda execucao de display(); a cor sera branca, caso contrario
    if (vez_disp == 2)
        glColor3f(1.0, 0.0, 0.0);

    glBegin(GL_POINTS);

    //para cada valor de raio considerado
    for (raio = RAIO_MIN; raio <= RAIO_MAX; raio += PASSO_RAIO) {

        //tempo medio das execucoes
        tempo_medio = 0.0;

        //coordenadas do ponto inicial
        x = 0;
        y = raio;

        //para cada execucao
        for (i = 0; i < NUM_REPETICOES; i++) {

            //registra-se o instante inicial em tinic
            clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);

            //para um oitavo da circunferencia, a ser refletido
            while (x < y) {

                //equacao retangular da circunferencia
                y = sqrt(pow(raio, 2) - pow(x, 2));

                //os oito pontos simetricos sao plotados. As coordenadas sao normalizadas para o tamanho da janela
                circlePoints(x / (LARGURA_JANELA / 2), y / (ALTURA_JANELA / 2));

                //a coordenada x e incrementada em 0.005
                x += 0.005;
            }

            //registra-se o instante final em tfinal 
            clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

            //o tempo medio das execucoes e incrementado
            tempo_medio += (float) diferencaEntreInstantes(tinic, tfinal).tv_nsec / NUM_REPETICOES;
        }

        //o valor do raio e o tempo medio de execucao a ele associado sao registrados em amostra
        amostras_alg_retangular->raio[k] = raio;
        amostras_alg_retangular->tempo_medio[k++] = tempo_medio / 1000;
    }

    glEnd();

    glFlush();

    //caso seja a segunda chamada a funcao display(), faz-se uma pausa de dois segundos
    if (vez_disp == 2)
        sleep(2);
}

//algoritmo incremental para conversao matricial das circunferencias, conforme apresentado em sala de aula

void circIncremental(amostra *amostras_alg_incremental) {

    int x, y, d, i, k = 0, raio;
    float tempo_medio;
    struct timespec tinic, tfinal;

    //limpa a tela
    glClear(GL_COLOR_BUFFER_BIT);

    //cor verde, se e a segunda execucao de display(); a cor sera branca, caso contrario
    if (vez_disp == 2)
        glColor3f(0.0, 1.0, 0.0);

    glBegin(GL_POINTS);

    //para cada valor de raio considerado
    for (raio = RAIO_MIN; raio <= RAIO_MAX; raio += PASSO_RAIO) {

        //tempo medio das execucoes
        tempo_medio = 0.0;

        //valores iniciais das coordenadas
        x = 0;
        y = raio;

        //incremento constante
        d = 1 - raio;

        //para cada execucao
        for (i = 0; i < NUM_REPETICOES; i++) {

            //registra-se o instante inicial em tinic
            clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);

            //os oito pontos simetricos sao plotados. As coordenadas sao normalizadas para o tamanho da janela
            circlePoints((float) x / (LARGURA_JANELA / 2), (float) y / (ALTURA_JANELA / 2));

            //para um oitavo da circunferencia, a ser refletido
            while (x < y) {

                if (d < 0) {

                    //seleciona E
                    d = d + 2 * x + 3;
                    x++;

                } else {

                    //seleciona SE
                    d = d + 2 * (x - y) + 5;
                    x++;
                    y--;
                }

                //os oito pontos simetricos sao plotados. As coordenadas sao normalizadas para o tamanho da janela
                circlePoints((float) x / (LARGURA_JANELA / 2), (float) y / (ALTURA_JANELA / 2));
            }

            //registra-se o instante final em tfinal 
            clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

            //o tempo medio das execucoes e incrementado
            tempo_medio += (float) diferencaEntreInstantes(tinic, tfinal).tv_nsec / NUM_REPETICOES;
        }

        //o valor do raio e o tempo medio de execucao a ele associado sao registrados em amostra
        amostras_alg_incremental->raio[k] = (float) raio;
        amostras_alg_incremental->tempo_medio[k++] = tempo_medio / 1000;
    }

    glEnd();

    glFlush();

    //caso seja a segunda chamada a funcao display(), faz-se uma pausa de dois segundos
    if (vez_disp == 2)
        sleep(2);
}

//funcao que especifica o que sera desenhado na tela

void display() {

    //amostras de tempo a serem recolhidas
    amostra amostras_alg_polar;
    amostra amostras_alg_retangular;
    amostra amostras_alg_incremental;

    //desenho na cor branca, exceto na segunda chamada a display(), quando os testes serao de fato realizados.
    //notamos que a funcao display sempre era evocada duas vezes quando o programa era posto em execucao
    glColor3f(255.0, 255.0, 255.0);

    if (vez_disp == 2) {

        //os testes sao realizados somente na segunda chamada a display; quando os realizamos na primeira chamada, encontramos erros
        printf("\n Testes em andamento... ");
        fflush(stdin);
    }

    //cada um dos algoritmos e evocado para a conversao das circunferencias, em sequencia
    circPolar(&amostras_alg_polar);
    circRetangular(&amostras_alg_retangular);
    circIncremental(&amostras_alg_incremental);

    //final dos testes
    if (vez_disp == 2) {

        printf("\n Testes realizados com succeso.");
        fflush(stdin);
    }

    //geracao do relatorio e dos graficos de resultados, somente na segunda chamada a display()
    if (vez_disp == 2) {

        printf("\n Gerando resultados...");
        fflush(stdin);
        geraRelatorioResultados(amostras_alg_retangular, amostras_alg_polar, amostras_alg_incremental);
        /*********************************************************************************************/
        //geracao dos graficos de resultados, com dislin - REMOVER CASO HAJA PROBLEMAS NA COMPILACAO
        //geraGraficosResultados(amostras_alg_retangular, amostras_alg_polar, amostras_alg_incremental);
        /*********************************************************************************************/
        printf("\n\n Resultados gerados. Os graficos e o relatorio encontram-se na pasta de seu programa executavel.");
        printf("\n\n");
        fflush(stdin);
        //a execucao do programa e terminada
        exit(0);
    }

    vez_disp++;
}

//quando a janela e redimensionada, faz-se com que ela retome seu tamanho original

void reshape(int w, int h) {

    glutReshapeWindow(LARGURA_JANELA, ALTURA_JANELA);

}

//funcao principal

void main(int argc, char *argv[]) {

    printf("********** Conversao Matricial de Circunferencias **********");

    printf("\n Pressione alguma tecla para iniciar os testes...");
    getchar();
    //inicia-se a execucao do programa

    /*inicio do glut e definicao de seus parametros*/
    glutInit(&argc, argv);
    vez_disp = 1;
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(LARGURA_JANELA, ALTURA_JANELA); //definicao do tamanho da janela
    glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - LARGURA_JANELA) / 2,
            (glutGet(GLUT_SCREEN_HEIGHT) - ALTURA_JANELA) / 2); //janela posiciona-se inicialmente ao centro da tela
    glutCreateWindow("Circunferencias");
    init2D(255, 255, 255);
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();
}
